package org.dyndns.opendemogroup.optimizer;

import java.util.Arrays;
import java.util.Random;

/**
 * A fixed-size collection of Member instances.
 */
public class Population
{
	// { fields
	public Member[] members;

	private int populationSize;

	private int memberSize;

	private double populationFitness;

	private double maxFitness;

	private double minFitness;

	private double averageFitness;

	private double bestFitness;

	private double worstFitness;

	// }

	// { properties
	public int getPopulationSize ( )
	{
		return populationSize;
	}

	public int getMemberSize ( )
	{
		return memberSize;
	}

	public double getAverageFitness ( )
	{
		return averageFitness;
	}

	public double getBestFitness ( )
	{
		return bestFitness;
	}

	public double getPopulationFitness ( )
	{
		return populationFitness;
	}

	public double getWorstFitness ( )
	{
		return worstFitness;
	}

	// }

	public Population ( int populationSize, int memberSize )
	{
		this.populationSize = populationSize;
		this.memberSize = memberSize;
		members = new Member[populationSize];
		for ( int i = 0; i < populationSize; i++ )
		{
			members[i] = new Member ( memberSize );
		}
	}

	public void sort ( )
	{
		Arrays.sort ( members, null );
	}

	public Member getBestMember ( IOptimizationProblem target )
	{
		int bestIndex = 0;
		if ( target.isMaximizing ( ) )
		{
			bestIndex = populationSize - 1;
		}
		Member result = members[bestIndex];
		return result;
	}

	public void computeFitnesses ( IOptimizationProblem target,
			Random randomSource )
	{
		// { initialization
		populationFitness = 0;
		maxFitness = Integer.MIN_VALUE;
		minFitness = Integer.MAX_VALUE;
		// }

		for ( int i = 0; i < populationSize; i++ )
		{
			Member m = members[i];
			boolean mustEncodeAgain = m.decode ( target, randomSource );
			if ( mustEncodeAgain )
			{
				m.encode ( target );
			}
			double currentFitness = m.getFitness ( );
			// TODO: also keep track of best member without first sorting
			populationFitness += currentFitness;
			maxFitness = Math.max ( maxFitness, currentFitness );
			minFitness = Math.min ( minFitness, currentFitness );
		}

		// { conclusion
		averageFitness = populationFitness / populationSize;
		if ( target.isMaximizing ( ) )
		{
			bestFitness = maxFitness;
			worstFitness = minFitness;
		}
		else
		{
			bestFitness = minFitness;
			worstFitness = maxFitness;
		}
		// }
	}

	public void initializeRandomPopulation ( Random randomSource,
			IOptimizationProblem problem )
	{
		// if problem implements ICustomInitialization, invoke it instead
		if ( problem instanceof ICustomInitialization )
		{
			ICustomInitialization initialization =
				(ICustomInitialization) problem;
			initialization.initialize ( randomSource, this );
		}
		else if ( !problem.isCombinatorialOptimization ( ) )
		{
			int geneRange = problem.getGeneRange ( );
			for ( int i = 0; i < populationSize; i++ )
			{
				Member m = members[i];
				for ( int j = 0; j < memberSize; j++ )
				{
					m.genome[j] = randomSource.nextInt ( geneRange + 1 );
				}
			}
		}
		else
		{
			for ( int i = 0; i < populationSize; i++ )
			{
				Member m = members[i];
				for ( int j = 0; j < memberSize; j++ )
				{
					m.genome[j] = j;
				}
				ArrayUtil.shuffle ( m.genome, randomSource );
			}
		}
	}

}
